const express = require('express');
const fs = require('fs-extra');
const path = require('path');
const ci = require('miniprogram-ci');
const multer = require('multer');
const yauzl = require('yauzl');

const app = express();
app.use(express.json());

// 配置文件上传
const upload = multer({
    dest: 'uploads/',
    limits: {
        fileSize: 100 * 1024 * 1024 // 限制100MB
    }
});

// 固定的API密钥
const API_SECRET_KEY = 'yueqiankejiyueqiankejiyueqiankeji';

// 配置CORS
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Content-Type, X-API-Key, Authorization');
    res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
    if (req.method === 'OPTIONS') {
        res.sendStatus(200);
    } else {
        next();
    }
});

// 密钥验证中间件
function validateApiKey(req, res, next) {
    const apiKey = req.headers['x-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
    
    if (!apiKey) {
        return res.status(401).json({
            success: false,
            message: '缺少API密钥，请在请求头中包含 X-API-Key',
            code: 'MISSING_API_KEY'
        });
    }
    
    if (apiKey !== API_SECRET_KEY) {
        return res.status(403).json({
            success: false,
            message: 'API密钥无效',
            code: 'INVALID_API_KEY'
        });
    }
    
    next();
}

/**
 * 解压ZIP文件到指定目录
 * @param {string} zipPath ZIP文件路径
 * @param {string} extractPath 解压目标路径
 */
function extractZip(zipPath, extractPath) {
    return new Promise((resolve, reject) => {
        yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
            if (err) return reject(err);
            
            zipfile.readEntry();
            zipfile.on("entry", (entry) => {
                if (/\/$/.test(entry.fileName)) {
                    // 目录
                    const dirPath = path.join(extractPath, entry.fileName);
                    fs.ensureDirSync(dirPath);
                    zipfile.readEntry();
                } else {
                    // 文件
                    zipfile.openReadStream(entry, (err, readStream) => {
                        if (err) return reject(err);
                        
                        const filePath = path.join(extractPath, entry.fileName);
                        fs.ensureDirSync(path.dirname(filePath));
                        
                        const writeStream = fs.createWriteStream(filePath);
                        readStream.pipe(writeStream);
                        writeStream.on('close', () => {
                            zipfile.readEntry();
                        });
                    });
                }
            });
            
            zipfile.on("end", () => {
                resolve();
            });
            
            zipfile.on("error", (err) => {
                reject(err);
            });
        });
    });
}

/**
 * 上传文件接口
 * POST /upload-file
 * 上传小程序代码包（ZIP文件）
 * 需要API密钥验证
 */
app.post('/upload-file', validateApiKey, upload.single('file'), async (req, res) => {
    try {
        if (!req.file) {
            return res.status(400).json({
                success: false,
                message: '请选择要上传的文件'
            });
        }

        // 检查hosting_id参数
        const hostingId = req.body.hosting_id;
        if (!hostingId) {
            // 删除上传的文件
            await fs.remove(req.file.path);
            return res.status(400).json({
                success: false,
                message: '缺少hosting_id参数'
            });
        }

        // 检查文件类型
        if (!req.file.originalname.toLowerCase().endsWith('.zip')) {
            // 删除上传的文件
            await fs.remove(req.file.path);
            return res.status(400).json({
                success: false,
                message: '只支持ZIP格式的文件'
            });
        }

        console.log('开始处理上传的文件:', req.file.originalname, '托管项目ID:', hostingId);

        // 生成新的文件夹名：code_hosting_id_时间戳
        const timestamp = Date.now();
        const folderName = `code_${hostingId}_${timestamp}`;
        const basePath = path.join(__dirname, 'code', folderName);
        const codePath = path.join(basePath, 'code');
        const codecopyPath = path.join(basePath, 'codecopy');

        // 检查并删除该hosting_id的旧文件夹
        const codeDir = path.join(__dirname, 'code');
        try {
            const existingFolders = await fs.readdir(codeDir);
            const hostingPattern = new RegExp(`^code_${hostingId}_\\d+$`);
            
            for (const folder of existingFolders) {
                if (hostingPattern.test(folder)) {
                    const oldFolderPath = path.join(codeDir, folder);
                    console.log('删除旧文件夹:', folder);
                    await fs.remove(oldFolderPath);
                }
            }
        } catch (error) {
            console.warn('清理旧文件夹时出错:', error.message);
            // 继续执行，不因为清理失败而中断上传
        }

        // 创建目录结构
        await fs.ensureDir(codePath);
        await fs.ensureDir(codecopyPath);

        // 解压文件到code目录
        console.log('解压文件到:', codePath);
        await extractZip(req.file.path, codePath);

        // 复制code内容到codecopy
        // console.log('复制代码到codecopy目录...');
        // await fs.copy(codePath, codecopyPath);

        // 删除临时上传文件
        await fs.remove(req.file.path);

        console.log('文件处理完成，文件夹:', folderName);

        res.json({
            success: true,
            message: '文件上传并解压成功',
            data: {
                folderName: folderName,
                hostingId: hostingId,
                timestamp: timestamp,
                originalName: req.file.originalname,
                uploadTime: new Date().toISOString()
            }
        });

    } catch (error) {
        console.error('文件上传失败:', error);

        // 清理临时文件
        try {
            if (req.file && req.file.path) {
                await fs.remove(req.file.path);
            }
        } catch (cleanupError) {
            console.error('清理临时文件失败:', cleanupError);
        }

        res.status(500).json({
            success: false,
            message: '文件上传失败',
            error: error.message
        });
    }
});

/**
 * 获取已上传的文件夹列表
 * GET /folders
 * 需要API密钥验证
 */
app.get('/folders', validateApiKey, async (req, res) => {
    try {
        const codeDir = path.join(__dirname, 'code');
        
        // 确保code目录存在
        await fs.ensureDir(codeDir);
        
        const folders = await fs.readdir(codeDir);
        
        // 过滤出code_开头的文件夹并获取详细信息
        const folderList = [];
        for (const folder of folders) {
            if (folder.startsWith('code_')) {
                const folderPath = path.join(codeDir, folder);
                const stat = await fs.stat(folderPath);
                
                if (stat.isDirectory()) {
                    folderList.push({
                        folderName: folder,
                        timestamp: folder.replace('code_', ''),
                        createTime: stat.birthtime.toISOString(),
                        modifyTime: stat.mtime.toISOString()
                    });
                }
            }
        }
        
        // 按时间戳倒序排列
        folderList.sort((a, b) => parseInt(b.timestamp) - parseInt(a.timestamp));

        res.json({
            success: true,
            message: '获取文件夹列表成功',
            data: {
                folders: folderList,
                total: folderList.length
            }
        });

    } catch (error) {
        console.error('获取文件夹列表失败:', error);
        res.status(500).json({
            success: false,
            message: '获取文件夹列表失败',
            error: error.message
        });
    }
});

/**
 * 上传小程序代码接口
 * POST /upload
 * 参数：
 * - appid: 小程序appid
 * - privateKey: 小程序上传密钥字符串
 * - url: 新的后端域名
 * - sourceDomain: 要被替换的原域名
 * - uniacid: uniacid数字
 * - entryPagePath: 首页路径
 * - version: 版本号
 * - desc: 版本描述（可选）
 * - xcx_id: 小程序托管ID（用于匹配文件夹 code_{xcx_id}_{timestamp}，使用其中的code目录）
 * 需要API密钥验证
 */
app.post('/upload', validateApiKey, async (req, res) => {
    const { appid, privateKey, url, sourceDomain, uniacid, entryPagePath, version, desc, xcx_id} = req.body;
    
    // 验证必需参数
    if (!appid || !privateKey || !url || !sourceDomain || !uniacid || !entryPagePath || !version || !xcx_id) {
        return res.status(400).json({
            success: false,
            message: '缺少必需参数：appid, privateKey, url, sourceDomain, uniacid, entryPagePath, version, xcx_id'
        });
    }
    
    // 根据 xcx_id 查找匹配的代码包文件夹
    let sourcePath;
    let matchedFolderName;
    
    try {
        const codeDir = path.join(__dirname, 'code');
        
        // 确保code目录存在
        if (!await fs.pathExists(codeDir)) {
            return res.status(400).json({
                success: false,
                message: `小程序托管ID为 ${xcx_id} 的代码包不存在，请先上传对应的代码包`
            });
        }
        
        // 读取code目录下的所有文件夹
        const folders = await fs.readdir(codeDir);
        
        // 过滤出匹配 code_{xcx_id}_{timestamp} 格式的文件夹
        const pattern = new RegExp(`^code_${xcx_id}_\\d+$`);
        const matchedFolders = folders.filter(folder => pattern.test(folder));
        
        if (matchedFolders.length === 0) {
            return res.status(400).json({
                success: false,
                message: `小程序托管ID为 ${xcx_id} 的代码包不存在，请先上传对应的代码包`
            });
        }
        
        // 按时间戳排序，选择最新的文件夹
        matchedFolders.sort((a, b) => {
            const timestampA = parseInt(a.split('_')[2]);
            const timestampB = parseInt(b.split('_')[2]);
            return timestampB - timestampA; // 降序，最新的在前
        });
        
        matchedFolderName = matchedFolders[0];
        sourcePath = path.join(codeDir, matchedFolderName, 'code');
        
        // 检查code目录是否存在
        if (!await fs.pathExists(sourcePath)) {
            return res.status(400).json({
                success: false,
                message: `找到匹配的代码包 ${matchedFolderName}，但其中的code目录不存在`
            });
        }
        
        console.log(`使用小程序托管ID ${xcx_id} 匹配到的代码包: ${matchedFolderName}`);
        
    } catch (error) {
        console.error('查找代码包时出错:', error);
        return res.status(500).json({
            success: false,
            message: '查找代码包时出错',
            error: error.message
        });
    }

    // 生成唯一的临时目录名，避免并发冲突
    const generateUniqueId = () => {
        const timestamp = Date.now(); // 毫秒级时间戳
        const randomSuffix = Math.random().toString(36).substr(2, 9); // 9位随机字符
        const processId = process.pid; // 进程ID
        return `${timestamp}-${processId}-${randomSuffix}`;
    };
    
    const uniqueId = generateUniqueId();
    const copyDirName = `code-copy-${uniqueId}`;
    const copyPath = path.join(__dirname, copyDirName);
    
    // 为了进一步确保安全，检查目录是否已存在（极低概率但防万一）
    if (await fs.pathExists(copyPath)) {
        return res.status(500).json({
            success: false,
            message: '临时目录名冲突，请重试',
            code: 'TEMP_DIR_CONFLICT'
        });
    }

    try {
        console.log('开始上传流程...');
        
        // 1. 复制找到的code目录到临时目录
        console.log('复制代码到临时目录...');
        await fs.copy(sourcePath, copyPath);
        
        // 2. 替换project.config.json中的appid
        console.log('替换project.config.json中的appid...');
        const projectConfigPath = path.join(copyPath, 'project.config.json');
        const projectConfig = await fs.readJson(projectConfigPath);
        projectConfig.appid = appid;
        await fs.writeJson(projectConfigPath, projectConfig, { spaces: 2 });
        
        // 3. 替换app.json中的entryPagePath
        console.log('替换app.json中的entryPagePath...');
        const appJsonPath = path.join(copyPath, 'app.json');
        const appJson = await fs.readJson(appJsonPath);
        appJson.entryPagePath = entryPagePath;
        await fs.writeJson(appJsonPath, appJson, { spaces: 2 });
        
        // 4. 替换common/vendor.js中的域名和uniacid
        console.log('替换vendor.js中的域名和uniacid...');
        const vendorJsPath = path.join(copyPath, 'common', 'vendor.js');
        let vendorContent = await fs.readFile(vendorJsPath, 'utf8');
        
        // 替换域名（将sourceDomain替换为url）
        console.log(`替换域名: ${sourceDomain} -> ${url}`);
        const escapedSourceDomain = sourceDomain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // 转义正则特殊字符
        const domainRegex = new RegExp(`https://${escapedSourceDomain}/`, 'g');
        vendorContent = vendorContent.replace(domainRegex, `https://${url}/`);
        
        // 替换uniacid
        vendorContent = vendorContent.replace(/uniacid:"6"/g, `uniacid:"${uniacid}"`);
        
        await fs.writeFile(vendorJsPath, vendorContent, 'utf8');
        
        // 5. 创建临时密钥文件（使用唯一ID避免冲突）
        console.log('创建临时密钥文件...');
        const keyPath = path.join(__dirname, `private.${appid}.${uniqueId}.key`);
        await fs.writeFile(keyPath, privateKey, 'utf8');
        
        // 6. 初始化项目对象
        console.log('初始化小程序项目...');
        const project = new ci.Project({
            appid: appid,
            type: 'miniProgram',
            projectPath: copyPath,
            privateKeyPath: keyPath,
            ignores: ['node_modules/**/*']
        });
        
        // 7. 上传代码
        console.log('开始上传小程序代码...');
        const uploadResult = await ci.upload({
            project,
            version: version,
            desc: desc,
            setting: {
                es6: true,
                es7: false,
                minifyJS: true,
                minifyWXML: true,
                minifyWXSS: true,
                minify: true,
                codeProtect: false,
                autoPrefixWXSS: true
            },
            onProgressUpdate: console.log
        });
        
        console.log('上传成功！');
        
        // 8. 清理临时文件
        console.log('清理临时文件...');
        await fs.remove(copyPath);  // 删除副本目录
        await fs.remove(keyPath);   // 删除密钥文件
        
        // 返回成功结果
        res.json({
            success: true,
            message: '小程序代码上传成功',
            data: {
                appid: appid,
                version: uploadResult.version || version,
                uploadTime: new Date().toISOString(),
                sourceFolder: matchedFolderName,
                xcx_id: xcx_id
            }
        });
        
    } catch (error) {
        console.error('上传失败:', error);
        
        // 清理临时文件（即使出错也要清理）
        try {
            if (await fs.pathExists(copyPath)) {
                await fs.remove(copyPath);
            }
            const keyPath = path.join(__dirname, `private.${appid}.${uniqueId}.key`);
            if (await fs.pathExists(keyPath)) {
                await fs.remove(keyPath);
            }
        } catch (cleanupError) {
            console.error('清理临时文件失败:', cleanupError);
        }
        
        res.status(500).json({
            success: false,
            message: '上传失败',
            error: error.message
        });
    }
});

// 健康检查接口
app.get('/health', (req, res) => {
    res.json({
        success: true,
        message: '小程序上传服务运行正常',
        timestamp: new Date().toISOString()
    });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`小程序上传服务已启动，端口: ${PORT}`);
    console.log(`健康检查: http://localhost:${PORT}/health`);
    console.log(`上传接口: http://localhost:${PORT}/upload`);
});

module.exports = app; 